在過往的網頁開發模式中,如果一個網站有 4 個不同的功能工具,傳統的做法往往是建立 4 個獨立的 HTML 檔案(例如 tool1.html、tool2.html)。當使用者點擊選單時,瀏覽器就會觸發整頁重新載入,不僅畫面會出現短暫的白頁閃爍,連帶運算狀態與 UI 互動感也會被打斷。
隨著前端技術的演進,單頁應用(Single Page Application, SPA) 成為現代 Web App 的標準配置。
來到鐵人賽的 Day 28,我們前幾天陸續實作完成的 4 大醫療保健輔助工具(生理指標計算器、兒童用藥劑量換算器、每日用藥時間表生成器、開瓶倒數計時器)已經全數完工。今天我們要進行重要的「全站系統整合」——撰寫全站主控腳本 app.js,透過純原生 JavaScript 實作 Tab 切換選單,打造免刷新的 SPA 流暢切換體驗!
在撰寫 Tab 切換邏輯時,許多初學者常犯的錯誤是「只顧著把點到的頁面打開,卻忘了把其他的頁面關掉」,導致多個工具重疊顯示在畫面上。
為了確保切換邏輯精準無誤,我們將切換流程拆解為以下兩大核心步驟:
當使用者點擊任意一個 Tab 按鈕時,第一時間清除所有按鈕與工具卡片的激活狀態:
.tab-btn 的 .active Class,並將無障礙屬性 aria-selected 統一重置為 "false"。.tool-card 的 .active Class,將所有工具頁面隱藏。歸零完成後,再針對「被點擊的按鈕」與「對應的目標工具卡片」進行狀態啟用:
.active,並將 aria-selected 設定為 "true"。data-target 或 data-tab),取得目標卡片的 ID。document.getElementById() 找到對應的 DOM 元素,加上 .activeClass 讓該工具頁面順暢顯現。我們在 /app.js 中監聽 DOMContentLoaded 事件,確保 DOM 節點載入完成後綁定事件監聽器:
/app.js
document.addEventListener("DOMContentLoaded", () => {
// 1. 抓取畫面上的按鈕與卡片
const tabBtns = document.querySelectorAll(".tab-btn");
const toolCards = document.querySelectorAll(".tool-card");
// 2. 監聽每個按鈕的點擊事件
tabBtns.forEach(btn => {
btn.addEventListener("click", () => {
// 3. 歸零:移除所有按鈕與卡片的 active 樣式,並將 aria-selected 設為 "false"
tabBtns.forEach(b => {
b.classList.remove("active");
b.setAttribute("aria-selected", "false");
});
toolCards.forEach(c => {
c.classList.remove("active");
});
// 4. 啟用:將被點擊的按鈕與對應的卡片加上 active 樣式,並將 aria-selected 設為 "true"
btn.classList.add("active");
btn.setAttribute("aria-selected", "true");
// 5. 讀取 HTML 屬性 (優先讀取 data-target,若無則讀取 data-tab)
const targetTab = btn.getAttribute("data-target") || btn.getAttribute("data-tab");
const targetCard = document.getElementById(targetTab);
if (targetCard) {
targetCard.classList.add("active");
} else {
console.error(`找不到 ID 為 "${targetTab}" 的工具卡片!請檢查 HTML 的 ID 命名。`);
}
});
});
});
DOMContentLoaded 確保 DOM 節點安全存取將切換邏輯包裹在 document.addEventListener("DOMContentLoaded", ...) 中,能確保 JavaScript 執行時,HTML 檔案中的 .tab-btn 與 .tool-card 節點都已經完成解析與繪製,徹底杜絕因腳本過早執行而抓取到 null 的經典 Bugs。
aria-selected 屬性動態同步在進行頁面切換時,除了變更 CSS Class(.active)來呈現視覺效果外,我們更同步更新了 b.setAttribute("aria-selected", "false") 與 btn.setAttribute("aria-selected", "true")。
這個細節對於使用螢幕閱讀器(Screen Readers)的視障使用者極為重要,能讓無障礙輔助工具即時感知當前聚焦的是哪一個 Tab 頁面,大大提升全站的無障礙友善度。
|| 實現 HTML 屬性命名向下相容(Fallback)在大型專案或團隊協作中,跨頁面的 HTML 屬性命名可能偶有不一致(例如有人寫 data-target="tool1",有人寫 data-tab="tool1")。
透過 btn.getAttribute("data-target") || btn.getAttribute("data-tab") 的備援機制,能優先讀取 data-target,若不存在則自動降級讀取 data-tab,極大地增強了腳本的容錯率與彈性。
當 DOM 結構找不到對應 ID 的卡片時,程式並不會安靜地失敗或引發不可預期的錯誤,而是透過 console.error(...) 輸出明確且友善的錯誤訊息,精準提示開發者「找不到哪個 ID」,讓除錯(Debugging)過程變得更加直覺高效。
今天我們透過純原生 JavaScript 完成了全站的 Tab 切換選單,以極簡的程式碼量實現了單頁應用(SPA)級別的切換順暢度,順利將 4 大工具無縫整合於單一介面之下。